#pragma once

#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <signal.h>
#include "Log.hpp"

//缺省值
const int defaultfd=-1;
const std::string defaultip="0.0.0.0";
const int backlog=10;//一般不要设置的太大

//初始化我们的日志
Log lg;

enum{
    SocketError=2,
    BindError,
    ListenError,
    UsageError
};

class ThreadData
{
public:
    ThreadData(int fd, const std::string &ip, const uint16_t &p, TcpServer *t): sockfd(fd), clientip(ip), clientport(p), tsvr(t)
    {}
    
public:
    int sockfd;
    std::string clientip;
    uint16_t clientport;
    TcpServer *tsvr;
};

class TcpServer
{
public:
    //构造函数
    TcpServer(const uint16_t &port,const std::string &ip=defaultip)
    :listensock_(defaultfd)
    ,port_(port)//初始化端口号
    ,ip_(ip)//初始化ip地址
    {
        //构造函数尽量不要做有风险的事情
    }
    
    //初始化
    void InitServer()
    {
        //创建套接字
        listensock_=socket(AF_INET,SOCK_STREAM,0);
        if(listensock_<0)
        {
            //创建失败打印日志
            lg(Fatal,"create socket, errno: %d, errstring: %s",errno,strerror(errno));
            exit(SocketError);
        }
        //创建成功打印日志
        lg(Info,"create socket success, listensockfd: %d",listensock_);

        //本地的套接字信息
        struct sockaddr_in local;
        memset(&local,0,sizeof(local));
        //填充网络信息
        local.sin_family=AF_INET;//iPv4的地址常数
        local.sin_port=htons(port_);//端口号要主机序列转换为网络序列
        inet_aton(ip_.c_str(),&(local.sin_addr));//传递ip地址
        //到这里套接字和本地信息就已经填充完了

        //local.sin_addr.s_addr=INADDR_ANY;

        //绑定套接字
        if(bind(listensock_,(struct sockaddr*)&local,sizeof(local))<0)
        {
            lg(Fatal,"bind error, errno: %d, errstring: %s",errno);
            exit(BindError);
        }
        //绑定套接字成功
        lg(Info,"bind socket success, listensockfd: %d",listensock_);

        //Tcp是面向连接的，服务器一般是比较被动的，就像开一个小店一样
        //服务器一直在等待连接到来的状态，要设置为监听状态
        if(listen(listensock_,backlog)<0)
        {
            lg(Fatal,"bind error, errno: %d, errstring: %s",errno);
            exit(ListenError);
        }

        //套接字监听成功
        lg(Info, "listen socket success, listensockfd: %d", listensock_);
        //完成了Init
    }

    static void *Routine(void *args)
    {
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData *>(args);
        td->tsvr->Service(td->sockfd, td->clientip, td->clientport);//???
        delete td;
        return nullptr;
    }

    void Start()
    {
        lg(Info,"tcpServer is running...");
        for(;;)
        {
            //1.获取新链接
            //tcp使用的接口也是和文件接口一样
            struct sockaddr_in client;
            socklen_t len=sizeof(client);
            int sockfd=accept(listensock_,(struct sockaddr *)&client,&len);
            //sockfd相当于服务员  sockfd_相当于顾客
            //sockfd套接字才是我们真正要给的
            if(sockfd<0)
            {
                //接客失败但是继续进行下一个
                lg(Warning, "listen error, error: %d, errstring: %s", errno,strerror(errno));
                continue;
            }
            //知道是谁连接的我
            uint16_t clientport=ntohs(client.sin_port);
            char clientip[32];
            inet_ntop(AF_INET,&(client.sin_addr),clientip,sizeof(clientip));

            //2.根据新链接来进行通信
            lg(Info,"get a new link..., sockfd: %d, client ip: %s, client port: %d\n",sockfd,clientip,clientport);

            //version 1 只有一个客人，单进程版
            //Service(sockfd,clientip,clientport);

            //version 2 多进程版，创建进程的成本太高了
            /*pid_t id = fork();
            if(id == 0)
            {
                // child
                close(listensock_);
                if(fork() > 0) exit(0);
                Service(sockfd, clientip, clientport); //孙子进程， system 领养
                close(sockfd);
                exit(0);
            }
            close(sockfd);
            // father
            pid_t rid = waitpid(id, nullptr, 0);//wait等待不会被阻塞
            (void)rid;
            */

            //version 3 -- 多线程版本
            ThreadData *td = new ThreadData(sockfd, clientip, clientport, this);
            pthread_t tid;
            pthread_create(&tid, nullptr, Routine, td);
        }
    }

    void Service(int sockfd,const std::string &clientip,const uint16_t clientport)
    {
        //测试代码
        char buffer[4096];//将网络信息读入buffer中
        while(true)
        {
            //文件读网络就像文件读取一样
            ssize_t n=read(sockfd,buffer,sizeof(buffer));
            if(n>0)
            {
                buffer[n]=0;
                std::cout<<"client say# "<<buffer<<std::endl;
                std::string echo_string="tcpserver echo# ";
                echo_string +=buffer;

                write(sockfd,echo_string.c_str(),echo_string.size());
            }
        }
    }

    ~TcpServer(){}

private:
    int listensock_;//socket返回的文件操作符
    uint16_t port_;//服务端进程的端口号
    std::string ip_;//字符串形式ip地址
};